
/******************************************************************************
 * MODULE     : tex_font.cpp
 * DESCRIPTION: TeX text fonts
 * COPYRIGHT  : (C) 1999  Joris van der Hoeven
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "Metafont/load_tex.hpp"
#include "analyze.hpp"
#include "colors.hpp"
#include "cork.hpp"
#include "font.hpp"
#include "iterator.hpp"
#include "translator.hpp"

#define TEX_ANY 0
#define TEX_EC 1
#define TEX_LA 2
#define TEX_GR 3
#define TEX_CM 4
#define TEX_ADOBE 5

int         get_utf8_code (string c);
static void special_initialize ();
font_metric tfm_font_metric (tex_font_metric tfm, font_glyphs pk, double unit);

hashmap<string, double> lsub_ecrm_table ();
hashmap<string, double> lsup_ecrm_table ();
hashmap<string, double> rsub_ecrm_table ();
hashmap<string, double> rsup_ecrm_table ();
hashmap<string, double> lsub_cmr_table ();
hashmap<string, double> lsup_cmr_table ();
hashmap<string, double> rsub_cmr_table ();
hashmap<string, double> rsup_cmr_table ();
hashmap<string, double> above_cmr_table ();
hashmap<string, double> lsub_cmmi_table ();
hashmap<string, double> lsup_cmmi_table ();
hashmap<string, double> rsub_cmmi_table ();
hashmap<string, double> rsup_cmmi_table ();
hashmap<string, double> above_cmmi_table ();
hashmap<string, double> lsub_cmsy_table ();
hashmap<string, double> lsup_cmsy_table ();
hashmap<string, double> above_cmsy_table ();
hashmap<string, double> lsub_bbm_table ();
hashmap<string, double> lsup_bbm_table ();
hashmap<string, double> rsub_bbm_table ();
hashmap<string, double> rsup_bbm_table ();
hashmap<string, double> above_bbm_table ();
hashmap<string, double> above_eufm_table ();
hashmap<string, double> rsub_rsfs_table ();
hashmap<string, double> rsup_rsfs_table ();
hashmap<string, double> above_rsfs_table ();

/******************************************************************************
 * TeX text fonts
 ******************************************************************************/

struct tex_font_rep : font_rep {
  int             status;
  string          family;
  int             dpi;
  int             dsize;
  tex_font_metric tfm;
  font_glyphs     pk;
  double          unit;
  bool            exec; // execute ligature and kerning program?

  tex_font_rep (string name, int status, string family, int size, int dpi,
                int dsize);

  bool  raw_supports (unsigned char c);
  bool  supports (string c);
  void  get_extents (string s, metric& ex);
  void  get_xpositions (string s, SI* xpos, bool ligf);
  void  get_xpositions (string s, SI* xpos);
  void  draw_fixed (renderer ren, string s, SI x, SI y);
  font  magnify (double zoomx, double zoomy);
  SI    get_left_correction (string s);
  SI    get_right_correction (string s);
  SI    get_lsub_correction (string s);
  SI    get_lsup_correction (string s);
  SI    get_rsub_correction (string s);
  SI    get_rsup_correction (string s);
  SI    get_wide_correction (string s, int mode);
  void  advance_glyph (string s, int& pos, bool ligf);
  glyph get_glyph (string s);
  int   index_glyph (string s, font_metric& fnm, font_glyphs& fng);
  int   get_ligature_code (string s);
  void  special_get_extents (string s, metric& ex);
  void  special_get_xpositions (string s, SI* xpos, bool ligf);
  void  special_draw (renderer ren, string s, SI x, SI y);
  SI    special_get_left_correction (string s);
  SI    special_get_right_correction (string s);
  void  accented_get_extents (string s, metric& ex);
  void  accented_get_xpositions (string s, SI* xpos, bool ligf);
  void  accented_draw (renderer ren, string s, SI x, SI y);
  SI    accented_get_left_correction (string s);
  SI    accented_get_right_correction (string s);
};

/******************************************************************************
 * The implementation of tex_fonts
 ******************************************************************************/

#define conv(x) ((SI) (((double) (x)) * unit))

tex_font_rep::tex_font_rep (string name, int status2, string family2, int size2,
                            int dpi2, int dsize2)
    : font_rep (name), status (status2), dsize (dsize2) {
  load_tex (family2, size2, dpi2, dsize, tfm, pk);

  family      = family2;
  type        = FONT_TYPE_TEX;
  size        = size2;
  dpi         = dpi2;
  design_size = tfm->design_size () >> 12;
  display_size= (((design_size * dpi) / 72) * PIXEL) >> 8;
  unit        = ((double) display_size) / ((double) (1 << 20));
  slope       = tfm->slope ();
  spc->def    = conv (tfm->spc ());
  spc->min    = spc->def - conv (tfm->spc_shrink ());
  spc->max    = spc->def + conv (tfm->spc_stretch ());
  extra       = conv (tfm->spc_extra ());
  extra->min  = extra->min >> 1;
  extra->max  = extra->min << 1;
  mspc        = spc;
  sep         = ((((dpi * PIXEL) / 72) * design_size) >> 8) / 10;
  exec        = !ends (family, "tt");

  y1          = conv (-262080);    // -0.25 quad
  y2          = y1 + display_size; //  0.75 quad
  yx          = conv (tfm->x_height ());
  yfrac       = yx >> 1;
  ysub_lo_base= -yx / 3;
  ysub_hi_lim = (5 * yx) / 6;
  ysup_lo_lim = yx / 2;
  ysup_lo_base= (5 * yx) / 6;
  ysup_hi_lim = yx;
  yshift      = yx / 6;

  wpt  = (dpi * PIXEL) / 72;
  hpt  = (dpi * PIXEL) / 72;
  wfn  = (wpt * design_size) >> 8;
  wline= wfn / 20;
  wquad= conv (tfm->spc_quad ());

  if ((family == "cmr") || (family == "ecrm") || (family == "cmmi")) {
    if (size < 8) wline= wfn / (size == 7 ? 16 : (size == 6 ? 14 : 12));
    else if (size < 10) yfrac+= (size * wfn) / 1600;
    else if (size <= 14) yfrac+= (size * wfn) / 1000;
    else {
      wline= wfn / (size > 16 ? 28 : 24);
      yfrac+= (size * wfn) / 700;
    }
  }

  special_initialize ();

  if (family == "ecrm" || family == "ecbx") {
    lsub_correct = lsub_ecrm_table ();
    lsup_correct = lsup_ecrm_table ();
    rsub_correct = rsub_ecrm_table ();
    rsup_correct = rsup_ecrm_table ();
    above_correct= hashmap<string, double> (0.0);
  }
  else if (family == "cmr" || family == "cmbx") {
    lsub_correct = lsub_cmr_table ();
    lsup_correct = lsup_cmr_table ();
    rsub_correct = rsub_cmr_table ();
    rsup_correct = rsup_cmr_table ();
    above_correct= above_cmr_table ();
  }
  else if (family == "cmmi" || family == "cmmib") {
    lsub_correct = lsub_cmmi_table ();
    lsup_correct = lsup_cmmi_table ();
    rsub_correct = rsub_cmmi_table ();
    rsup_correct = rsup_cmmi_table ();
    above_correct= above_cmmi_table ();
  }
  else if (family == "cmsy" || family == "cmbsy") {
    lsub_correct = lsub_cmsy_table ();
    lsup_correct = lsup_cmsy_table ();
    rsub_correct = hashmap<string, double> (0.0);
    rsup_correct = hashmap<string, double> (0.0);
    above_correct= above_cmsy_table ();
  }
  else if (family == "bbm" || family == "bbmbx") {
    lsub_correct = lsub_bbm_table ();
    lsup_correct = lsup_bbm_table ();
    rsub_correct = rsub_bbm_table ();
    rsup_correct = rsup_bbm_table ();
    above_correct= above_bbm_table ();
  }
  else if (family == "eufm" || family == "eufb") {
    lsub_correct = hashmap<string, double> (0.0);
    lsup_correct = hashmap<string, double> (0.0);
    rsub_correct = hashmap<string, double> (0.0);
    rsup_correct = hashmap<string, double> (0.0);
    above_correct= above_eufm_table ();
  }
  else if (family == "rsfs") {
    yx           = (SI) round (0.53 * display_size);
    yfrac        = yx >> 1;
    ysub_lo_base = -yx / 3;
    ysub_hi_lim  = (5 * yx) / 6;
    ysup_lo_lim  = yx / 2;
    ysup_lo_base = (5 * yx) / 6;
    ysup_hi_lim  = yx;
    yshift       = yx / 6;
    lsub_correct = hashmap<string, double> (0.0);
    lsup_correct = hashmap<string, double> (0.0);
    rsub_correct = rsub_rsfs_table ();
    rsup_correct = rsup_rsfs_table ();
    above_correct= above_rsfs_table ();
  }
  else {
    lsub_correct = hashmap<string, double> (0.0);
    lsup_correct = hashmap<string, double> (0.0);
    rsub_correct = hashmap<string, double> (0.0);
    rsup_correct = hashmap<string, double> (0.0);
    above_correct= hashmap<string, double> (0.0);
  }
}

/******************************************************************************
 * Handle <, > and (in the future?) other special characters
 ******************************************************************************/

static bool                    special_initialized= false;
static hashmap<string, string> special_translate ("");

static void
special_initialize (string enc) {
  translator       trl= load_translator (enc);
  iterator<string> it = iterate (trl->dict);
  while (it->busy ()) {
    string s             = it->next ();
    special_translate (s)= string ((char) (unsigned char) trl->dict[s]);
    if (N (s) > 2 && s (0, 2) == "<#") {
      string sl= locase_all (s), su= upcase_all (s);
      special_translate (sl)= string ((char) (unsigned char) trl->dict[s]);
      special_translate (su)= string ((char) (unsigned char) trl->dict[s]);
    }
  }
}

static void
special_initialize () {
  if (special_initialized) return;
  special_translate ("<less>")= "<";
  special_translate ("<gtr>") = ">";
  special_initialize ("larm");
  special_initialize ("grmn");
  special_initialized= true;
}

void
tex_font_rep::special_get_extents (string s, metric& ex) {
  int i, j;
  for (i= 0; i < N (s); i++)
    if (s[i] == '<') break;
  get_extents (s (0, i), ex);
  for (j= i + 1; j < N (s); j++)
    if (s[j] == '>') break;
  if (j < N (s)) j++;

  SI     x;
  metric ey;
  int    temp= status;
  status     = TEX_ANY;
  string r   = s (i, j);
  string rr  = special_translate[r];
  if (N (rr) != 0) r= rr;
  get_extents (r, ey);
  x     = ex->x2;
  ex->x1= min (ex->x1, x + ey->x1);
  ex->y1= min (ex->y1, ey->y1);
  ex->x2= max (ex->x2, x + ey->x2);
  ex->y2= max (ex->y2, ey->y2);
  ex->x3= min (ex->x3, x + ey->x3);
  ex->y3= min (ex->y3, ey->y3);
  ex->x4= max (ex->x4, x + ey->x4);
  ex->y4= max (ex->y4, ey->y4);
  status= temp;

  get_extents (s (j, N (s)), ey);
  x     = ex->x2;
  ex->x1= min (ex->x1, x + ey->x1);
  ex->y1= min (ex->y1, ey->y1);
  ex->x2= max (ex->x2, x + ey->x2);
  ex->y2= max (ex->y2, ey->y2);
  ex->x3= min (ex->x3, x + ey->x3);
  ex->y3= min (ex->y3, ey->y3);
  ex->x4= max (ex->x4, x + ey->x4);
  ex->y4= max (ex->y4, ey->y4);
}

void
tex_font_rep::special_get_xpositions (string s, SI* xpos, bool ligf) {
  SI  offset= 0;
  int l= 0, i, j, n= N (s);
  while (l < n) {
    for (i= l; i < n; i++)
      if (s[i] == '<') break;
    if (l < i) {
      get_xpositions (s (l, i), xpos + l, ligf);
      for (j= l; j <= i; j++)
        xpos[j]+= offset;
      if (i == n) break;
      offset= xpos[i];
    }

    for (j= i + 1; j < n; j++) {
      xpos[j]= offset;
      if (s[j] == '>') break;
    }
    if (j < n) j++;
    metric ey;
    int    temp= status;
    status     = TEX_ANY;
    string r   = s (i, j);
    string rr  = special_translate[r];
    if (N (rr) != 0) r= rr;
    get_extents (r, ey);
    status= temp;
    offset+= ey->x2;
    xpos[j]= offset;
    l      = j;
  }
}

void
tex_font_rep::special_draw (renderer ren, string s, SI x, SI y) {
  int    i, j;
  metric ex;
  for (i= 0; i < N (s); i++)
    if (s[i] == '<') break;
  draw_fixed (ren, s (0, i), x, y);
  get_extents (s (0, i), ex);
  x+= ex->x2;
  for (j= i + 1; j < N (s); j++)
    if (s[j] == '>') break;
  if (j < N (s)) j++;

  int temp  = status;
  status    = TEX_ANY;
  string r  = s (i, j);
  string rr = special_translate[r];
  pencil pen= ren->get_pencil ();
  if (N (rr) != 0) r= rr;
  else ren->set_pencil (red);
  draw_fixed (ren, r, x, y);
  ren->set_pencil (pen);
  get_extents (r, ex);
  x+= ex->x2;
  status= temp;

  draw_fixed (ren, s (j, N (s)), x, y);
}

SI
tex_font_rep::special_get_left_correction (string s) {
  int i= 0;
  tm_char_forwards (s, i);
  string r= special_translate (s (0, i));
  if (N (r) != 0) return (SI) (slope * conv (tfm->d ((QN) r[0])));
  return (SI) (slope * conv (tfm->d ((QN) '<')));
}

SI
tex_font_rep::special_get_right_correction (string s) {
  int n= N (s), i= n;
  tm_char_backwards (s, i);
  string r= special_translate (s (i, n));
  if (N (r) != 0) return conv (tfm->i ((QN) r[0]));
  return conv (tfm->i ((QN) '>'));
}

/******************************************************************************
 * Handle accents
 ******************************************************************************/

static char CM_unaccented[128]= {
    'A',   ' ',   'C',   'C',   'D',   'E', ' ',   'G',  'L', 'L', ' ',   'N',
    'N',   ' ',   'O',   'R',   'R',   'S', 'S',   'S',  'T', 'T', 'U',   'U',
    'Y',   'Z',   'Z',   'Z',   ' ',   'I', 'd',   ' ',  'a', ' ', 'c',   'c',
    'd',   'e',   ' ',   'g',   'l',   'l', ' ',   'n',  'n', ' ', 'o',   'r',
    'r',   's',   's',   's',   't',   't', 'u',   'u',  'y', 'z', 'z',   'z',
    ' ',   '\74', '\76', ' ',   'A',   'A', 'A',   'A',  'A', 'A', '\35', 'C',
    'E',   'E',   'E',   'E',   'I',   'I', 'I',   'I',  'D', 'N', 'O',   'O',
    'O',   'O',   'O',   '\36', '\37', 'U', 'U',   'U',  'U', 'Y', ' ',   ' ',
    'a',   'a',   'a',   'a',   'a',   'a', '\32', 'c',  'e', 'e', 'e',   'e',
    '\20', '\20', '\20', '\20', 'd',   'n', 'o',   'o',  'o', 'o', 'o',   '\33',
    '\34', 'u',   'u',   'u',   'u',   'y', ' ',   '\31'};

static char CM_accents[128]= {
    '\25',  ' ',    '\23',  '\24',  '\24',  '\24',  ' ',    '\25',  '\23',
    '\47',  ' ',    '\23',  '\24',  ' ',    '\175', '\23',  '\24',  '\23',
    '\24',  '\30',  '\24',  '\30',  '\175', '\27',  '\177', '\23',  '\24',
    '\137', ' ',    '\137', '\26',  ' ',    '\25',  ' ',    '\23',  '\24',
    '\24',  '\24',  ' ',    '\25',  '\23',  '\47',  ' ',    '\23',  '\24',
    ' ',    '\175', '\23',  '\24',  '\23',  '\24',  '\30',  '\24',  '\30',
    '\175', '\27',  '\177', '\23',  '\24',  '\137', ' ',    ' ',    ' ',
    ' ',    '\22',  '\23',  '\136', '\176', '\177', '\27',  ' ',    '\30',
    '\22',  '\23',  '\136', '\177', '\22',  '\23',  '\136', '\177', '\26',
    '\176', '\22',  '\23',  '\136', '\176', '\177', ' ',    ' ',    '\22',
    '\23',  '\136', '\177', '\23',  ' ',    ' ',    '\22',  '\23',  '\136',
    '\176', '\177', '\27',  ' ',    '\30',  '\22',  '\23',  '\136', '\177',
    '\22',  '\23',  '\136', '\177', '\26',  '\176', '\22',  '\23',  '\136',
    '\176', '\177', ' ',    ' ',    '\22',  '\23',  '\136', '\177', '\23',
    ' ',    ' '};

static char ADOBE_unaccented[128]= {
    'A',    'A',    'C',    'C',    'D', 'E', 'E', 'G',   'L',    'L',
    '\350', 'N',    'N',    ' ',    'O', 'R', 'R', 'S',   'S',    'S',
    'T',    'T',    'U',    'U',    'Y', 'Z', 'Z', 'Z',   ' ',    'I',
    'd',    '\247', 'a',    'a',    'c', 'c', 'd', 'e',   'e',    'g',
    'l',    'l',    '\370', 'n',    'n', ' ', 'o', 'r',   'r',    's',
    's',    's',    't',    't',    'u', 'u', 'y', 'z',   'z',    'z',
    ' ',    '\241', '\277', '\243', 'A', 'A', 'A', 'A',   'A',    'A',
    '\341', 'C',    'E',    'E',    'E', 'E', 'I', 'I',   'I',    'I',
    'D',    'N',    'O',    'O',    'O', 'O', 'O', '\36', '\351', 'U',
    'U',    'U',    'U',    'Y',    ' ', ' ', 'a', 'a',   'a',    'a',
    'a',    'a',    '\361', 'c',    'e', 'e', 'e', 'e',   '\365', '\365',
    '\365', '\365', 'd',    'n',    'o', 'o', 'o', 'o',   'o',    '\372',
    '\371', 'u',    'u',    'u',    'u', 'y', ' ', '\373'};

static char ADOBE_accents[128]= {
    '\306', '\316', '\302', '\317', '\317', '\317', '\316', '\306', '\302',
    '\47',  ' ',    '\302', '\317', ' ',    '\315', '\302', '\317', '\302',
    '\317', '\313', '\317', '\313', '\315', '\312', '\310', '\302', '\317',
    '\307', ' ',    '\307', '\305', ' ',    '\306', '\316', '\302', '\317',
    '\317', '\317', '\316', '\306', '\302', '\47',  ' ',    '\302', '\317',
    ' ',    '\315', '\302', '\317', '\302', '\317', '\313', '\317', '\313',
    '\315', '\312', '\310', '\302', '\317', '\307', ' ',    ' ',    ' ',
    ' ',    '\301', '\302', '\303', '\304', '\310', '\312', ' ',    '\313',
    '\301', '\302', '\303', '\310', '\301', '\302', '\303', '\310', '\305',
    '\304', '\301', '\302', '\303', '\304', '\310', ' ',    ' ',    '\301',
    '\302', '\303', '\310', '\302', ' ',    ' ',    '\301', '\302', '\303',
    '\304', '\310', '\312', ' ',    '\313', '\301', '\302', '\303', '\310',
    '\301', '\302', '\303', '\310', '\305', '\304', '\301', '\302', '\303',
    '\304', '\310', ' ',    ' ',    '\301', '\302', '\303', '\310', '\302',
    ' ',    ' '};

static char* the_unaccented;
static char* the_accents;

#define ACCENTS_PREPARE                                                        \
  if (status == TEX_CM) {                                                      \
    the_unaccented= CM_unaccented;                                             \
    the_accents   = CM_accents;                                                \
  }                                                                            \
  else {                                                                       \
    the_unaccented= ADOBE_unaccented;                                          \
    the_accents   = ADOBE_accents;                                             \
  }

static string
get_unaccented (string s) {
  int    i;
  string r (N (s));
  for (i= 0; i < N (s); i++)
    if ((s[i] & 128) == 0) r[i]= s[i];
    else {
      char c= the_unaccented[s[i] & 127];
      if (c == ' ') r[i]= s[i];
      else r[i]= the_unaccented[s[i] & 127];
    }
  return r;
}

static string
get_accents (string s) {
  int    i, n= N (s);
  string r (n);
  for (i= 0; i < n; i++) {
    if ((s[i] & 128) == 0) r[i]= ' ';
    else r[i]= (char) the_accents[s[i] & 127];
  }
  return r;
}

void
tex_font_rep::accented_get_extents (string s, metric& ex) {
  int old_status= status;
  status        = TEX_ANY;

  int    i;
  string acc= get_accents (s);
  s         = get_unaccented (s);
  get_extents (s, ex);

  for (i= 0; i < N (acc); i++)
    if (acc[i] != ' ') {
      SI     xx, yy;
      char   c= acc[i];
      metric ey, ez;
      get_extents (s (0, i + 1), ey);
      xx= ey->x2;
      get_extents (s[i], ey);
      get_extents (c, ez);
      xx-= (((ey->x2 - ey->x1) + (ez->x2 - ez->x1)) >> 1);
      yy= ey->y2 - yx;
      if (c == 24) yy= PIXEL;
      else if (c == ((char) 203)) yy= 0;
      else if (c == ((char) 206)) {
        yy= 0;
        if ((s[i] == 'a') || (s[i] == 'A')) xx+= (ey->x2 - ey->x1) / 3;
        else xx+= (ey->x2 - ey->x1) / 5;
      }
      else xx+= (SI) (((double) yy) * slope);
      ex->x3= min (ex->x3, xx + ez->x3);
      ex->y3= min (ex->y3, yy + ez->y3);
      ex->x4= max (ex->x4, xx + ez->x4);
      ex->y4= max (ex->y4, yy + ez->y4);
    }

  status= old_status;
}

void
tex_font_rep::accented_get_xpositions (string s, SI* xpos, bool ligf) {
  int old_status= status;
  status        = TEX_ANY;
  string acc    = get_accents (s);
  s             = get_unaccented (s);
  get_xpositions (s, xpos, ligf);
  status= old_status;
}

void
tex_font_rep::accented_draw (renderer ren, string s, SI x, SI y) {
  int old_status= status;
  status        = TEX_ANY;

  int    i;
  string acc= get_accents (s);
  s         = get_unaccented (s);
  draw_fixed (ren, s, x, y);

  for (i= 0; i < N (acc); i++)
    if (acc[i] != ' ') {
      SI     xx, yy;
      char   c= acc[i];
      metric ey, ez;
      get_extents (s (0, i + 1), ey);
      xx= ey->x2;
      get_extents (s[i], ey);
      get_extents (c, ez);
      xx-= (((ey->x2 - ey->x1) + (ez->x2 - ez->x1)) >> 1);
      yy= ey->y2 - yx;
      if (c == 24) yy= PIXEL;
      else if (c == ((char) 203)) yy= 0;
      else if (c == ((char) 206)) {
        yy= 0;
        if ((s[i] == 'a') || (s[i] == 'A')) xx+= (ey->x2 - ey->x1) / 3;
        else xx+= (ey->x2 - ey->x1) / 5;
      }
      else xx+= (SI) (((double) yy) * slope);
      draw_fixed (ren, string (c), x + xx, y + yy);
    }

  status= old_status;
}

SI
tex_font_rep::accented_get_left_correction (string s) {
  s= get_unaccented (s);
  return (SI) (slope * conv (tfm->d ((QN) s[0])));
}

SI
tex_font_rep::accented_get_right_correction (string s) {
  s= get_unaccented (s);
  return conv (tfm->i ((QN) s[N (s) - 1]));
}

/******************************************************************************
 * The general case
 ******************************************************************************/

bool
tex_font_rep::raw_supports (unsigned char c) {
  glyph gl= pk->get ((int) c);
  return !is_nil (gl);
}

bool
tex_font_rep::supports (string s) {
  switch (status) {
  case TEX_ANY:
    if (s == "<less>") return raw_supports ('<');
    else if (s == "<gtr>") return raw_supports ('>');
    else if (N (s) == 1) return raw_supports (s[0]);
    else if (starts (s, "<#") && N (s) == 7) {
      int code= get_utf8_code (s);
      return (code > 0) && (code < 256) && raw_supports ((unsigned char) code);
    }
    else return false;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    if (N (s) == 1 || s == "<less>" || s == "<gtr>") {
      return true;
    }
    else if (starts (s, "<#") && N (s) == 7) {
      int code= get_utf8_code (s);
      return (code > 0) && (code < 256) && raw_supports ((unsigned char) code);
    }
    else {
      return false;
    }
  case TEX_CM:
  case TEX_ADOBE:
    if (N (s) != 1) return s == "<less>" || s == "<gtr>";
    else if (((unsigned int) s[0]) < ((unsigned int) 128)) return true;
    else {
      ACCENTS_PREPARE;
      return get_accents (s) != " ";
    }
  }
  return false;
}

void
tex_font_rep::get_extents (string s, metric& ex) {
  if (starts (s, "<#") && N (s) == 7) {
    int code= get_utf8_code (s);
    get_extents (string ((char) code), ex);
    return;
  }
  int i;
  switch (status) {
  case TEX_ANY:
    break;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    for (i= 0; i < N (s); i++)
      if (s[i] == '<') {
        special_get_extents (s, ex);
        return;
      }
    break;
  case TEX_CM:
  case TEX_ADOBE:
    for (i= 0; i < N (s); i++) {
      if (s[i] == '<') {
        special_get_extents (s, ex);
        return;
      }
      if ((s[i] & 128) != 0) {
        ACCENTS_PREPARE;
        accented_get_extents (s, ex);
        return;
      }
    }
    break;
  }

  int n= N (s);
  int m= (n + 16) << 1;
  STACK_NEW_ARRAY (s_copy, int, n);
  STACK_NEW_ARRAY (buf, int, m);
  STACK_NEW_ARRAY (ker, int, m);

  if (exec) {
    for (i= 0; i < n; i++)
      s_copy[i]= ((QN) s[i]);
    tfm->execute (s_copy, n, buf, ker, m);
  }
  else {
    m= n;
    for (i= 0; i < m; ++i) {
      buf[i]= s[i] & 255;
      ker[i]= 0;
    }
  }

  SI x1= 0;
  SI x2= 0;
  SI x3= PLUS_INFINITY;
  SI x4= MINUS_INFINITY;
  SI y1= PLUS_INFINITY;
  SI y2= MINUS_INFINITY;
  SI y3= PLUS_INFINITY;
  SI y4= MINUS_INFINITY;

  for (i= 0; i < m; i++) {
    int   c = buf[i];
    glyph gl= pk->get (c);
    if (is_nil (gl)) continue;

    y1= min (y1, -conv (tfm->d (c)));
    y2= max (y2, conv (tfm->h (c)));
    x3= min (x3, x2 - ((int) gl->xoff) * PIXEL);
    x4= max (x4, x2 + ((int) (gl->width - gl->xoff)) * PIXEL);
    y3= min (y3, ((int) (gl->yoff - gl->height)) * PIXEL);
    y4= max (y4, ((int) gl->yoff) * PIXEL);
    x2+= conv (tfm->w (c) + ker[i]);
  }

  if ((x3 == PLUS_INFINITY) || (x4 == MINUS_INFINITY) ||
      (y3 == PLUS_INFINITY) || (y4 == MINUS_INFINITY)) {
    x1= x3= x2= x4= 0;
    y3= y1= 0;
    y4= y2= yx;
  }

  ex->x1= x1;
  ex->x2= x2;
  ex->x3= x3 - 2 * PIXEL;
  ex->x4= x4 + 2 * PIXEL;
  ex->y1= y1;
  ex->y2= y2;
  ex->y3= y3 - 2 * PIXEL;
  ex->y4= y4 + 2 * PIXEL;

  STACK_DELETE_ARRAY (s_copy);
  STACK_DELETE_ARRAY (buf);
  STACK_DELETE_ARRAY (ker);
}

void
tex_font_rep::get_xpositions (string s, SI* xpos, bool ligf) {
  int i, n= N (s);
  xpos[0]= 0;
  if (n == 0) return;

  switch (status) {
  case TEX_ANY:
    break;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    for (i= 0; i < n; i++)
      if (s[i] == '<') {
        special_get_xpositions (s, xpos, ligf);
        return;
      }
    break;
  case TEX_CM:
  case TEX_ADOBE:
    for (i= 0; i < n; i++) {
      if (s[i] == '<') {
        special_get_xpositions (s, xpos, ligf);
        return;
      }
      if ((s[i] & 128) != 0) {
        ACCENTS_PREPARE;
        accented_get_xpositions (s, xpos, ligf);
        return;
      }
    }
    break;
  }

  if (starts (s, "<#") && N (s) == 7) {
    int code= get_utf8_code (s);
    STACK_NEW_ARRAY (s_copy, int, 1);
    s_copy[0]= (QN) code;
    tfm->get_xpositions (s_copy, 1, unit, xpos, ligf);
    STACK_DELETE_ARRAY (s_copy);
    xpos[7]= xpos[1];
    return;
  }

  STACK_NEW_ARRAY (s_copy, int, n);
  for (i= 0; i < n; i++)
    s_copy[i]= ((QN) s[i]);
  tfm->get_xpositions (s_copy, n, unit, xpos, ligf);
  STACK_DELETE_ARRAY (s_copy);
}

void
tex_font_rep::get_xpositions (string s, SI* xpos) {
  get_xpositions (s, xpos, true);
}

void
tex_font_rep::draw_fixed (renderer ren, string s, SI ox, SI y) {
  if (starts (s, "<#") && N (s) == 7) {
    int   code= get_utf8_code (s);
    glyph gl  = pk->get (code);
    ren->draw (code, pk, ox, y);
    return;
  }
  int i;
  switch (status) {
  case TEX_ANY:
    break;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    for (i= 0; i < N (s); i++)
      if (s[i] == '<') {
        special_draw (ren, s, ox, y);
        return;
      }
    break;
  case TEX_CM:
  case TEX_ADOBE:
    for (i= 0; i < N (s); i++) {
      if (s[i] == '<') {
        special_draw (ren, s, ox, y);
        return;
      }
      if ((s[i] & 128) != 0) {
        ACCENTS_PREPARE;
        accented_draw (ren, s, ox, y);
        return;
      }
    }
    break;
  }

  SI  x= ox;
  int n= N (s);
  int m= (n + 16) << 1;
  STACK_NEW_ARRAY (str, int, n);
  STACK_NEW_ARRAY (buf, int, m);
  STACK_NEW_ARRAY (ker, int, m);

  if (exec) {
    for (i= 0; i < n; i++)
      str[i]= ((QN) s[i]);
    tfm->execute (str, n, buf, ker, m);
  }
  else {
    m= n;
    for (i= 0; i < m; ++i) {
      buf[i]= s[i] & 255;
      ker[i]= 0;
    }
  }

  for (i= 0; i < m; i++) {
    int   c = buf[i];
    glyph gl= pk->get (c);
    if (is_nil (gl)) continue;
    ren->draw (c, pk, x, y);
    x+= conv (tfm->w (c) + ker[i]);
  }
  STACK_DELETE_ARRAY (str);
  STACK_DELETE_ARRAY (buf);
  STACK_DELETE_ARRAY (ker);
}

font
tex_font_rep::magnify (double zoomx, double zoomy) {
  if (zoomx != zoomy) return poor_magnify (zoomx, zoomy);
  int ndpi= (int) tm_round (dpi * zoomx);
  switch (status) {
  case TEX_ANY:
    return tex_font (family, size, ndpi, dsize);
  case TEX_EC:
    return tex_ec_font (family, size, ndpi, dsize);
  case TEX_LA:
    return tex_la_font (family, size, ndpi, dsize);
  case TEX_GR:
    return tex_gr_font (family, size, ndpi, dsize);
  case TEX_CM:
    return tex_cm_font (family, size, ndpi, dsize);
  case TEX_ADOBE:
    return tex_adobe_font (family, size, ndpi, dsize);
  }
  return tex_font (family, size, ndpi, dsize);
}

SI
tex_font_rep::get_left_correction (string s) {
  if (N (s) == 0) return 0;
  switch (status) {
  case TEX_ANY:
    break;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    if (s[0] == '<') return special_get_left_correction (s);
    break;
  case TEX_CM:
  case TEX_ADOBE:
    if (s[0] == '<') return special_get_left_correction (s);
    if ((s[0] & 128) != 0) {
      ACCENTS_PREPARE;
      return accented_get_left_correction (s);
    }
  }
  return (SI) (slope * conv (tfm->d ((QN) s[0])));
}

SI
tex_font_rep::get_right_correction (string s) {
  if (N (s) == 0) return 0;
  switch (status) {
  case TEX_ANY:
    break;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    if (s[N (s) - 1] == '>') return special_get_right_correction (s);
    break;
  case TEX_CM:
  case TEX_ADOBE:
    if (s[N (s) - 1] == '>') return special_get_right_correction (s);
    if ((s[N (s) - 1] & 128) != 0) {
      ACCENTS_PREPARE;
      return accented_get_right_correction (s);
    }
  }
  return conv (tfm->i ((QN) s[N (s) - 1]));
}

SI
tex_font_rep::get_lsub_correction (string s) {
  SI r= -get_left_correction (s);
  if (lsub_correct->contains (s)) r+= (SI) (lsub_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[0]) && lsub_correct->contains (s (0, 1)))
    r+= (SI) (lsub_correct[s (0, 1)] * wfn);
  return r;
}

SI
tex_font_rep::get_lsup_correction (string s) {
  SI r= 0;
  if (lsup_correct->contains (s)) r+= (SI) (lsup_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[0]) && lsup_correct->contains (s (0, 1)))
    r+= (SI) (lsup_correct[s (0, 1)] * wfn);
  return r;
}

SI
tex_font_rep::get_rsub_correction (string s) {
  SI r= 0;
  if (rsub_correct->contains (s)) r+= (SI) (rsub_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[N (s) - 1]) &&
           rsub_correct->contains (s (N (s) - 1, N (s))))
    r+= (SI) (rsub_correct[s (N (s) - 1, N (s))] * wfn);
  return r;
}

SI
tex_font_rep::get_rsup_correction (string s) {
  SI r= get_right_correction (s);
  if (rsup_correct->contains (s)) r+= (SI) (rsup_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[N (s) - 1]) &&
           rsup_correct->contains (s (N (s) - 1, N (s))))
    r+= (SI) (rsup_correct[s (N (s) - 1, N (s))] * wfn);
  return r;
}

SI
tex_font_rep::get_wide_correction (string s, int mode) {
  if (mode > 0 && above_correct->contains (s))
    return (SI) (above_correct[s] * wfn);
  else if (mode < 0 && below_correct->contains (s))
    return (SI) (below_correct[s] * wfn);
  else return 0;
}

void
tex_font_rep::advance_glyph (string s, int& pos, bool ligf) {
  if (pos >= N (s)) return;
  if (!exec || (status != TEX_ANY && s[pos] == '<') || !ligf)
    tm_char_forwards (s, pos);
  else {
    int c    = -1;
    int start= pos;
    while (pos < N (s)) {
      int prev= pos;
      tm_char_forwards (s, pos);
      string r= s (start, pos);

      int n= N (r);
      int m= (n + 16) << 1;
      STACK_NEW_ARRAY (str, int, n);
      STACK_NEW_ARRAY (buf, int, m);
      STACK_NEW_ARRAY (ker, int, m);
      for (int i= 0; i < n; i++)
        str[i]= ((QN) r[i]);
      tfm->execute (str, n, buf, ker, m);
      bool done= (m > 0 && buf[0] == c);
      if (!done && m > 0) c= buf[0];
      STACK_DELETE_ARRAY (str);
      STACK_DELETE_ARRAY (buf);
      STACK_DELETE_ARRAY (ker);

      if (done) {
        pos= prev;
        break;
      }
    }
  }
}

glyph
tex_font_rep::get_glyph (string s) {
  int i;
  switch (status) {
  case TEX_ANY:
    break;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    if (s == "<less>") s= "<";
    else if (s == "<gtr>") s= ">";
    else if (N (s) >= 2 && s[0] == '<') s= special_translate[s];
    break;
  case TEX_CM:
  case TEX_ADOBE:
    if (s == "<less>") s= "<";
    if (s == "<gtr>") s= ">";
    for (i= 0; i < N (s); i++)
      if ((s[i] & 128) != 0) return font_rep::get_glyph (s);
    break;
  }
  int c;
  if (N (s) != 1) c= get_ligature_code (s);
  else c= ((QN) s[0]);
  if (c == -1) return font_rep::get_glyph (s);
  glyph gl= pk->get (c);
  if (is_nil (gl)) return font_rep::get_glyph (s);
  return gl;
}

int
tex_font_rep::index_glyph (string s, font_metric& rm, font_glyphs& rg) {
  int i;
  switch (status) {
  case TEX_ANY:
    break;
  case TEX_EC:
  case TEX_LA:
  case TEX_GR:
    if (s == "<less>") s= "<";
    else if (s == "<gtr>") s= ">";
    else if (N (s) >= 2 && s[0] == '<') s= special_translate[s];
    break;
  case TEX_CM:
  case TEX_ADOBE:
    if (s == "<less>") s= "<";
    if (s == "<gtr>") s= ">";
    for (i= 0; i < N (s); i++)
      if ((s[i] & 128) != 0 && s[i] != ((char) 254))
        return font_rep::index_glyph (s, rm, rg);
    break;
  }
  int c;
  if (N (s) != 1) c= get_ligature_code (s);
  else c= ((QN) s[0]);
  if (c == -1) return font_rep::index_glyph (s, rm, rg);
  glyph gl= pk->get (c);
  if (is_nil (gl)) {
    if (c == ((QN) 254)) return -1;
    return font_rep::index_glyph (s, rm, rg);
  }
  rm= tfm_font_metric (tfm, pk, unit);
  rg= pk;
  return c;
}

int
tex_font_rep::get_ligature_code (string s) {
  int n= N (s);
  int m= (n + 16) << 1;
  STACK_NEW_ARRAY (str, int, n);
  STACK_NEW_ARRAY (buf, int, m);
  STACK_NEW_ARRAY (ker, int, m);
  for (int i= 0; i < n; i++)
    str[i]= ((QN) s[i]);
  tfm->execute (str, n, buf, ker, m);
  STACK_DELETE_ARRAY (str);
  STACK_DELETE_ARRAY (buf);
  STACK_DELETE_ARRAY (ker);
  if (m == 1) return buf[0];
  else return -1;
}

/******************************************************************************
 * TeX font metrics as usual metrics
 ******************************************************************************/

extern metric error_metric;

struct tfm_font_metric_rep : public font_metric_rep {
  tex_font_metric       tfm;
  font_glyphs           pk;
  double                unit;
  hashmap<int, pointer> ms;
  tfm_font_metric_rep (string name, tex_font_metric tfm2, font_glyphs pk2,
                       double unit2)
      : font_metric_rep (name), tfm (tfm2), pk (pk2), unit (unit2),
        ms (error_metric) {}
  bool exists (int c) { return c >= ((int) tfm->bc) && ((int) tfm->ec) >= c; }
  metric& get (int c) {
    if (!exists (c)) return error_metric;
    if (!ms->contains (c)) {
      metric_struct* r= tm_new<metric_struct> ();
      ms (c)          = (pointer) r;
      r->x1           = 0;
      r->x2           = conv (tfm->w (c));
      r->y1           = -conv (tfm->d (c));
      r->y2           = conv (tfm->h (c));
      glyph gl        = pk->get (c);
      r->x3           = -((int) gl->xoff) * PIXEL;
      r->x4           = ((int) (gl->width - gl->xoff)) * PIXEL;
      r->y3           = ((int) (gl->yoff - gl->height)) * PIXEL;
      r->y4           = ((int) gl->yoff) * PIXEL;
    }
    return *((metric*) ((void*) ms[c]));
  }
};

#undef conv

font_metric
tfm_font_metric (tex_font_metric tfm, font_glyphs pk, double unit) {
  string name= tfm->res_name * ":" * pk->res_name * ":" * as_string (unit);
  return make (font_metric, name,
               tm_new<tfm_font_metric_rep> (name, tfm, pk, unit));
}

/******************************************************************************
 * Interface
 ******************************************************************************/

font
tex_font (string family, int size, int dpi, int dsize) {
  string name= "tex:" * family * as_string (size) * "@" * as_string (dpi);
  return make (font, name,
               tm_new<tex_font_rep> (name, TEX_ANY, family, size, dpi, dsize));
}

font
tex_cm_font (string family, int size, int dpi, int dsize) {
  string name= "cm:" * family * as_string (size) * "@" * as_string (dpi);
  return make (font, name,
               tm_new<tex_font_rep> (name, TEX_CM, family, size, dpi, dsize));
}

font
tex_ec_font (string family, int size, int dpi, int dsize) {
  string name= "ec:" * family * as_string (size) * "@" * as_string (dpi);
  return make (font, name,
               tm_new<tex_font_rep> (name, TEX_EC, family, size, dpi, dsize));
}

font
tex_la_font (string family, int size, int dpi, int dsize) {
  string name= "la:" * family * as_string (size) * "@" * as_string (dpi);
  return make (font, name,
               tm_new<tex_font_rep> (name, TEX_LA, family, size, dpi, dsize));
}

font
tex_gr_font (string family, int size, int dpi, int dsize) {
  string name= "gr:" * family * as_string (size) * "@" * as_string (dpi);
  return make (font, name,
               tm_new<tex_font_rep> (name, TEX_GR, family, size, dpi, dsize));
}

font
tex_adobe_font (string family, int size, int dpi, int dsize) {
  string name= "adobe:" * family * as_string (size) * "@" * as_string (dpi);
  return make (
      font, name,
      tm_new<tex_font_rep> (name, TEX_ADOBE, family, size, dpi, dsize));
}
